pi-openui 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +167 -2
- package/package.json +54 -4
- package/src/blocks/braille.ts +121 -0
- package/src/blocks/bubble.ts +30 -0
- package/src/blocks/charts.ts +370 -0
- package/src/blocks/code.ts +101 -0
- package/src/blocks/core.ts +343 -0
- package/src/blocks/diagrams.ts +142 -0
- package/src/blocks/geo.ts +206 -0
- package/src/blocks/interact.ts +252 -0
- package/src/blocks/markdown.ts +135 -0
- package/src/blocks/notes.ts +277 -0
- package/src/blocks/render.ts +148 -0
- package/src/blocks/sequence.ts +266 -0
- package/src/blocks/table.ts +70 -0
- package/src/blocks/types.ts +126 -0
- package/src/blocks/util.ts +171 -0
- package/src/data/land.ts +3 -0
- package/src/data/sample-image.ts +67 -0
- package/src/diagram/canvas.ts +182 -0
- package/src/diagram/dag.ts +174 -0
- package/src/diagram/decode.ts +241 -0
- package/src/diagram/erd.ts +190 -0
- package/src/diagram/infra.ts +444 -0
- package/src/diagram/layered.ts +334 -0
- package/src/diagram/layeredDiagram.ts +350 -0
- package/src/diagram/palette.ts +209 -0
- package/src/diagram/render.ts +60 -0
- package/src/diagram/router.ts +402 -0
- package/src/diagram/wires.ts +271 -0
- package/src/lang/docs.ts +575 -0
- package/src/lang/library.ts +613 -0
- package/src/lang/md.ts +47 -0
- package/src/lang/parse.ts +230 -0
- package/src/lang/samples.ts +227 -0
- package/src/pi/extension.ts +190 -0
- package/src/pi/images.ts +185 -0
- package/src/pi/widget.ts +562 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.1 (2026-10-30)
|
|
6
|
+
|
|
7
|
+
- The README now renders on pi.dev and npm: screenshots are markdown images (pi.dev drops raw HTML tables and images) and
|
|
8
|
+
links to the documentation are absolute (pi.dev rewrote the relative ones to a CDN copy of the package, which has no `docs/`).
|
|
9
|
+
Nothing changes in the extension.
|
|
10
|
+
|
|
11
|
+
## 0.1.0 (2026-10-30)
|
|
12
|
+
|
|
13
|
+
- **Notes.** Pin a comment to a table cell, node, slice, point or paragraph; the notes go into pi's message box as one numbered
|
|
14
|
+
message, never straight to the agent. `/openui-notes`, a `[ notes ]` chip under every widget that has something to note, pins
|
|
15
|
+
and a tray. Mouse and keyboard (Tab to `[ Add ... ]`, or Down from a note, then Enter). Words from the widget are cleaned
|
|
16
|
+
before they reach the message. See docs/interaction.md.
|
|
17
|
+
- **Pictures in Markdown.** A line that is only `` draws a picture, in Markdown and in FAQ answers, through
|
|
18
|
+
the same code as `Image`. A missing file or a web URL is an error the assistant is told about.
|
|
19
|
+
- **Size limits** for every list and string, as validation errors with the number in them, listed in the model's reference and in
|
|
20
|
+
docs/components/README.md. `compile()` can no longer throw. At most 12 pictures per widget.
|
|
21
|
+
- **Cheaper to draw.** An unchanged widget costs nothing per frame (it was 8 ms for 17 widgets); layouts are kept per width;
|
|
22
|
+
terminal cells are shared and frozen (300 widgets: 47 MB down to 17 MB); wire routing shares a work budget (a 120-node
|
|
23
|
+
infrastructure map no longer freezes pi for 5 s); the picture cache is bounded by bytes; session state is dropped at shutdown.
|
|
24
|
+
- The last column of every line is left empty, because pi's scrollbar is painted over it. It cut the `]` off the notes chip.
|
|
25
|
+
- MIT license.
|
|
26
|
+
- Installable with `pi install git:...`: a `pi` manifest and peer dependencies in `package.json`. See docs/releasing.md.
|
|
27
|
+
- Fixed: an armed widget kept Enter after "Add to message", so the Enter meant to send the message would have pressed a widget
|
|
28
|
+
element.
|
|
29
|
+
|
|
30
|
+
### First version
|
|
31
|
+
|
|
32
|
+
- `render_widget` tool and the commands `/openui-gallery`, `/openui-file` and `/openui-focus`.
|
|
33
|
+
- 23 components: Stack, Text, Card, Callout, Metric, Table, Tabs, Button, Choice, Faq, Markdown, CodeBlock, Image, BarChart,
|
|
34
|
+
LineChart, AreaChart, PieChart, RadarChart, GeoMap, Dag, Erd, InfraMap, SequenceDiagram, plus Tooltip.
|
|
35
|
+
- Mouse and keyboard interaction, a hover bubble, tooltip panels, a copy button, sideways scrolling for wide diagrams.
|
|
36
|
+
- Tooltips on Dag, Erd, InfraMap, SequenceDiagram, Table, Metric, BarChart, LineChart, AreaChart, PieChart, RadarChart, GeoMap
|
|
37
|
+
and Image, marked with a `◆`. A target that matches nothing is a validation error that lists the valid ones.
|
|
38
|
+
- Pictures through pi-tui's image support (Kitty protocol), framed, with tooltips and a labelled fallback.
|
|
39
|
+
- ERD relation ends are one cell each: `├ ┤ ┬ ┴` for one and `< > Λ V` for many.
|
|
40
|
+
- Every glyph is audited against JetBrains Mono Nerd Font Mono.
|
|
41
|
+
- Generated component pages with screenshots (`docs/components/`), checked by the tests.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Dai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,168 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-openui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Interactive dashboards, charts, diagrams and maps in the terminal, for the [pi](https://github.com/earendil-works/pi)
|
|
4
|
+
coding agent. The agent writes a few lines of OpenUI Lang. pi draws a widget you can click or drive with the keyboard.
|
|
5
|
+
|
|
6
|
+
It uses unicode characters, and real pictures where the terminal can show them. There is no browser, no HTML, no runtime
|
|
7
|
+
dependency and no network access.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
*Screenshots are drawn by `npm run screenshots` from the widget's real output, with pi's dark theme and JetBrains Mono.*
|
|
12
|
+
|
|
13
|
+
## What you get
|
|
14
|
+
|
|
15
|
+
- **One tool, `render_widget`.** The agent calls it with OpenUI Lang. Mistakes come back as a list of errors with line
|
|
16
|
+
numbers, so it fixes them and tries again. After a successful draw it says one short sentence.
|
|
17
|
+
- **23 components.** Layout and text, metric tiles, tables, tabs, buttons and choices, FAQ, markdown, code with
|
|
18
|
+
highlighting and a copy button, images, bar, line, area, pie and radar charts, a world map, and four diagram types
|
|
19
|
+
(DAG, ER, infrastructure map, sequence). See the [component gallery](https://github.com/newtonapple/pi-openui/blob/main/docs/components/README.md).
|
|
20
|
+
- **Interaction.** Click, or use Tab, the arrow keys, Enter and Esc. A button or a choice sends text to the agent as your next
|
|
21
|
+
message. See [docs/interaction.md](https://github.com/newtonapple/pi-openui/blob/main/docs/interaction.md).
|
|
22
|
+
- **Notes.** Pin a comment to a table cell, a graph node or a pie slice, then send all of them to the agent as one numbered
|
|
23
|
+
message. You read and edit the message before it goes. See [Notes](#notes).
|
|
24
|
+
- **Tooltips.** An element with a tooltip shows a small `◆`. Hover for a hint bubble, click or press Enter for a panel with
|
|
25
|
+
detail, code and a link. See [Tooltip](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Tooltip.md).
|
|
26
|
+
- **Pictures.** An `Image` draws the real picture in terminals that speak the Kitty graphics protocol, inside a frame that
|
|
27
|
+
can carry a tooltip. A line of Markdown that is only a picture, ``, is drawn the same way, so a
|
|
28
|
+
README-style view is one `Markdown`. See [Image](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Image.md) and [Markdown](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Markdown.md).
|
|
29
|
+
- **Robust diagrams.** Every graph, schema and map is routed on a character grid with one wire per edge. The tests decode
|
|
30
|
+
the finished picture back into its edges and compare them with the input. See [docs/diagrams.md](https://github.com/newtonapple/pi-openui/blob/main/docs/diagrams.md).
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pi install npm:pi-openui
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That is all. There is nothing to build, and no dependencies to fetch: pi supplies the packages the extension imports. Start pi
|
|
39
|
+
and run `/openui-gallery`. It needs pi 1.4 or newer.
|
|
40
|
+
|
|
41
|
+
| What | Command |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Install from npm | `pi install npm:pi-openui` |
|
|
44
|
+
| Install one version | `pi install npm:pi-openui@0.1.0` |
|
|
45
|
+
| Install from GitHub instead | `pi install git:github.com/newtonapple/pi-openui` (add `@v0.1.0` to pin a tag) |
|
|
46
|
+
| Try it for one session | `pi -e npm:pi-openui` |
|
|
47
|
+
| Install from a clone | `pi install /path/to/pi-openui` |
|
|
48
|
+
| See what is installed | `pi list` |
|
|
49
|
+
| Update | `pi update --extensions` |
|
|
50
|
+
| Remove | `pi remove npm:pi-openui` |
|
|
51
|
+
|
|
52
|
+
It is in pi's [package catalog](https://pi.dev/packages), which lists npm packages that carry the `pi-package` keyword. After an
|
|
53
|
+
update pi shows this package's [changelog](CHANGELOG.md). Maintainers: see [docs/releasing.md](https://github.com/newtonapple/pi-openui/blob/main/docs/releasing.md).
|
|
54
|
+
|
|
55
|
+
## Use it
|
|
56
|
+
|
|
57
|
+
| What | How |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Let the agent draw | Ask for a dashboard, a chart, a diagram, a table. It calls `render_widget`. |
|
|
60
|
+
| One example of every component | `/openui-gallery`, or one kind: `/openui-gallery dag`, `erd`, `infra`, `sequence`, `geo`, `pie`, `line`, `image`, `table`, `tabs`, `markdown` |
|
|
61
|
+
| Draw a file | `/openui-file path/to/widget.oui` |
|
|
62
|
+
| Use the keyboard without the mouse | `/openui-focus` |
|
|
63
|
+
| Write notes on a widget | Click `[ notes ]` under it, or `/openui-notes` |
|
|
64
|
+
|
|
65
|
+
Gallery clicks are echoed locally and cost nothing. In a widget the agent drew, a Button or Choice click is sent to the
|
|
66
|
+
agent as your next message.
|
|
67
|
+
|
|
68
|
+
The mouse needs pi's default fullscreen mode. Everything also works from the keyboard.
|
|
69
|
+
|
|
70
|
+
## Notes
|
|
71
|
+
|
|
72
|
+
Pin a comment to one part of a widget, then send all your comments to the agent as a single message. Use it to say "this row
|
|
73
|
+
is wrong" or "explain this node" without describing where it is.
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
1. Click `[ notes ]` under a widget, or run `/openui-notes`.
|
|
78
|
+
2. Click a cell, node, slice or paragraph, or Tab to it and press Enter. pi's own editor opens. Type, press Enter.
|
|
79
|
+
3. Click `[ Add 2 notes to your message ]`, or Tab to it and press Enter. The notes land in pi's message box as a numbered list.
|
|
80
|
+
4. Read it, edit it, press Enter. Nothing is sent before that.
|
|
81
|
+
|
|
82
|
+
Each note names its target the way the agent wrote it, for example `tbl` (Table) > row 3, column `State`, so the agent can find
|
|
83
|
+
the line in its own source and answer by number. Words that come from the widget are cleaned before they reach your message,
|
|
84
|
+
so a widget cannot slip an `@file` reference or a code span into something you are about to send.
|
|
85
|
+
|
|
86
|
+
A widget holds 20 notes. They live in memory and are gone when the session ends. Keys, what each component lets you pick, and
|
|
87
|
+
the details are in [docs/interaction.md](https://github.com/newtonapple/pi-openui/blob/main/docs/interaction.md#notes).
|
|
88
|
+
|
|
89
|
+
## Components
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
All 23, with signatures, every argument, a checked example and tips: **[docs/components/](https://github.com/newtonapple/pi-openui/blob/main/docs/components/README.md)**.
|
|
94
|
+
|
|
95
|
+
| Group | Components |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Layout and text | [Stack](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Stack.md), [Text](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Text.md), [Card](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Card.md), [Callout](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Callout.md) |
|
|
98
|
+
| Data | [Metric](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Metric.md), [Table](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Table.md) |
|
|
99
|
+
| Interactive | [Tabs](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Tabs.md), [Button](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Button.md), [Choice](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Choice.md), [Faq](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Faq.md) |
|
|
100
|
+
| Rich content | [Markdown](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Markdown.md), [CodeBlock](https://github.com/newtonapple/pi-openui/blob/main/docs/components/CodeBlock.md), [Image](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Image.md) |
|
|
101
|
+
| Charts | [BarChart](https://github.com/newtonapple/pi-openui/blob/main/docs/components/BarChart.md), [LineChart](https://github.com/newtonapple/pi-openui/blob/main/docs/components/LineChart.md), [AreaChart](https://github.com/newtonapple/pi-openui/blob/main/docs/components/AreaChart.md), [PieChart](https://github.com/newtonapple/pi-openui/blob/main/docs/components/PieChart.md), [RadarChart](https://github.com/newtonapple/pi-openui/blob/main/docs/components/RadarChart.md) |
|
|
102
|
+
| Diagrams and maps | [Dag](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Dag.md), [Erd](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Erd.md), [InfraMap](https://github.com/newtonapple/pi-openui/blob/main/docs/components/InfraMap.md), [SequenceDiagram](https://github.com/newtonapple/pi-openui/blob/main/docs/components/SequenceDiagram.md), [GeoMap](https://github.com/newtonapple/pi-openui/blob/main/docs/components/GeoMap.md) |
|
|
103
|
+
|
|
104
|
+
[Tooltip](https://github.com/newtonapple/pi-openui/blob/main/docs/components/Tooltip.md) attaches to Dag, Erd, InfraMap, SequenceDiagram, Table, Metric, BarChart,
|
|
105
|
+
LineChart, AreaChart, PieChart, RadarChart, GeoMap and Image.
|
|
106
|
+
|
|
107
|
+
## How it works
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
OpenUI Lang --parse--> typed tree --render--> blocks (rows of cells + click targets) --> pi-tui lines
|
|
111
|
+
^ |
|
|
112
|
+
interaction logic: hit test, keys, panning <-- mouse and keys
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `src/lang/` parses and validates OpenUI Lang with one table of component signatures. The same table writes the reference the
|
|
116
|
+
agent reads and the docs pages you see here.
|
|
117
|
+
- `src/blocks/` turns each component into a grid of styled cells plus the places you can click.
|
|
118
|
+
- `src/diagram/` is the layout and routing engine for Dag, Erd and InfraMap.
|
|
119
|
+
- `src/pi/` is the pi extension: the tool, the commands, the themed component, mouse and keyboard handling, pictures.
|
|
120
|
+
|
|
121
|
+
More in [docs/design.md](https://github.com/newtonapple/pi-openui/blob/main/docs/design.md).
|
|
122
|
+
|
|
123
|
+
## Requirements
|
|
124
|
+
|
|
125
|
+
- pi 1.4 or newer.
|
|
126
|
+
- Node 22.18 or newer for the tests and scripts (they run TypeScript directly). Developed on Node 26.
|
|
127
|
+
- A terminal font with the glyphs the widgets use. They were chosen from, and are tested against, JetBrains Mono Nerd Font
|
|
128
|
+
Mono. A different font works if it has them: `PI_OPENUI_FONT=/path/to/font.ttf npm test` checks yours.
|
|
129
|
+
- Pictures need a terminal that supports the Kitty graphics protocol. Other terminals get a framed placeholder.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
Working on the code? Start pi in this folder instead of installing it: `.pi/extensions/openui.ts` loads the extension (pi asks
|
|
134
|
+
once whether to trust the folder). To load it from somewhere else for one session:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
pi -e /path/to/pi-openui/.pi/extensions/openui.ts
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
After you change the code, run `/reload` in pi. The rest:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npm test # a few seconds, no network, no model calls
|
|
144
|
+
npm run docs # regenerate docs/components/ from the signature table
|
|
145
|
+
npm run screenshots # regenerate docs/screenshots/, and the two README composites (needs Python with Pillow, and the font)
|
|
146
|
+
npm run gallery # print every component to the terminal (-- --plain for no colour)
|
|
147
|
+
make help # the release process (check, version, publish, tag, push)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Tests are described in [docs/testing.md](https://github.com/newtonapple/pi-openui/blob/main/docs/testing.md). Releases are described in [docs/releasing.md](https://github.com/newtonapple/pi-openui/blob/main/docs/releasing.md).
|
|
151
|
+
Contributors and agents working in this repository should read [AGENTS.md](https://github.com/newtonapple/pi-openui/blob/main/AGENTS.md).
|
|
152
|
+
|
|
153
|
+
## Limits
|
|
154
|
+
|
|
155
|
+
- Hover needs a terminal that reports mouse movement. The keyboard covers everything else.
|
|
156
|
+
- Only Kitty-protocol terminals show pictures. A tooltip attaches to a whole picture, not to a spot inside it.
|
|
157
|
+
- Dense graphs of 11 or more nodes can be wider than the terminal. They scroll sideways, and moving focus scrolls the node
|
|
158
|
+
into view.
|
|
159
|
+
- The map uses 110m coastlines: country scale, not city scale.
|
|
160
|
+
- Widgets are not saved anywhere but pi's own session. A resumed session redraws them from the tool results. Notes are not
|
|
161
|
+
saved at all.
|
|
162
|
+
- A widget has size limits (100 KB of source, 100 table rows, 60 graph nodes, 30 map nodes, and so on). Over a limit the
|
|
163
|
+
agent gets an error with the number and fixes it. They keep pi fast: it draws every widget on every frame. See the
|
|
164
|
+
[list](https://github.com/newtonapple/pi-openui/blob/main/docs/components/README.md#size-limits).
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
[MIT](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-openui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pi extension: interactive dashboards, charts, diagrams and maps in the terminal. The agent draws with OpenUI Lang; you click, hover and pin notes.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "David Dai",
|
|
8
|
+
"homepage": "https://github.com/newtonapple/pi-openui#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/newtonapple/pi-openui.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/newtonapple/pi-openui/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi-extension",
|
|
19
|
+
"pi",
|
|
20
|
+
"pi-coding-agent",
|
|
21
|
+
"coding-agent",
|
|
22
|
+
"openui",
|
|
23
|
+
"tui",
|
|
24
|
+
"terminal",
|
|
25
|
+
"dashboard",
|
|
26
|
+
"charts",
|
|
27
|
+
"diagrams",
|
|
28
|
+
"widgets",
|
|
29
|
+
"visualization"
|
|
30
|
+
],
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./src/pi/extension.ts"
|
|
34
|
+
],
|
|
35
|
+
"image": "https://raw.githubusercontent.com/newtonapple/pi-openui/main/docs/screenshots/dashboard.png"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
39
|
+
"@earendil-works/pi-tui": "*",
|
|
40
|
+
"typebox": "*"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"CHANGELOG.md"
|
|
45
|
+
],
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "node --import ./test/register-pi.mjs --test --test-reporter=spec test/*.test.ts",
|
|
51
|
+
"gallery": "node scripts/langgallery.ts",
|
|
52
|
+
"screenshots": "node --import ./test/register-pi.mjs scripts/shots.ts && python3 scripts/render_shots.py && python3 scripts/hero.py",
|
|
53
|
+
"docs": "node scripts/gen_docs.ts",
|
|
54
|
+
"prepublishOnly": "npm test"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// A dot canvas drawn with braille characters: 2 x 4 dots per cell, square dots in a 1:2 cell.
|
|
2
|
+
import { cell, EMPTY, type Cell } from "../diagram/canvas.ts";
|
|
3
|
+
|
|
4
|
+
const BITS = [
|
|
5
|
+
[0x01, 0x02, 0x04, 0x40],
|
|
6
|
+
[0x08, 0x10, 0x20, 0x80],
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export class Dots {
|
|
10
|
+
readonly cols: number;
|
|
11
|
+
readonly rows: number;
|
|
12
|
+
readonly w: number;
|
|
13
|
+
readonly h: number;
|
|
14
|
+
readonly bits: Uint8Array;
|
|
15
|
+
readonly owner: Int16Array;
|
|
16
|
+
constructor(cols: number, rows: number) {
|
|
17
|
+
this.cols = cols;
|
|
18
|
+
this.rows = rows;
|
|
19
|
+
this.w = cols * 2;
|
|
20
|
+
this.h = rows * 4;
|
|
21
|
+
this.bits = new Uint8Array(cols * rows);
|
|
22
|
+
this.owner = new Int16Array(cols * rows).fill(-1);
|
|
23
|
+
}
|
|
24
|
+
set(x: number, y: number, owner: number): void {
|
|
25
|
+
x = Math.round(x);
|
|
26
|
+
y = Math.round(y);
|
|
27
|
+
if (x < 0 || y < 0 || x >= this.w || y >= this.h) return;
|
|
28
|
+
const c = (y >> 2) * this.cols + (x >> 1);
|
|
29
|
+
this.bits[c] |= BITS[x & 1][y & 3];
|
|
30
|
+
this.owner[c] = owner;
|
|
31
|
+
}
|
|
32
|
+
line(x0: number, y0: number, x1: number, y1: number, owner: number): void {
|
|
33
|
+
x0 = Math.round(x0);
|
|
34
|
+
y0 = Math.round(y0);
|
|
35
|
+
x1 = Math.round(x1);
|
|
36
|
+
y1 = Math.round(y1);
|
|
37
|
+
const dx = Math.abs(x1 - x0);
|
|
38
|
+
const dy = -Math.abs(y1 - y0);
|
|
39
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
40
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
41
|
+
let err = dx + dy;
|
|
42
|
+
for (let guard = 0; guard < 100000; guard++) {
|
|
43
|
+
this.set(x0, y0, owner);
|
|
44
|
+
if (x0 === x1 && y0 === y1) break;
|
|
45
|
+
const e2 = 2 * err;
|
|
46
|
+
if (e2 >= dy) {
|
|
47
|
+
err += dy;
|
|
48
|
+
x0 += sx;
|
|
49
|
+
}
|
|
50
|
+
if (e2 <= dx) {
|
|
51
|
+
err += dx;
|
|
52
|
+
y0 += sy;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
rows2cells(roleFor: (owner: number) => string): Cell[][] {
|
|
57
|
+
const out: Cell[][] = [];
|
|
58
|
+
for (let r = 0; r < this.rows; r++) {
|
|
59
|
+
const row: Cell[] = [];
|
|
60
|
+
for (let c = 0; c < this.cols; c++) {
|
|
61
|
+
const b = this.bits[r * this.cols + c];
|
|
62
|
+
row.push(b ? cell(String.fromCharCode(0x2800 + b), roleFor(this.owner[r * this.cols + c])) : EMPTY);
|
|
63
|
+
}
|
|
64
|
+
out.push(row);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function niceNum(x: number, round: boolean): number {
|
|
71
|
+
if (x <= 0) return 1;
|
|
72
|
+
const exp = Math.floor(Math.log10(x));
|
|
73
|
+
const f = x / 10 ** exp;
|
|
74
|
+
const nf = round ? (f < 1.5 ? 1 : f < 3 ? 2 : f < 7 ? 5 : 10) : f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10;
|
|
75
|
+
return nf * 10 ** exp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function fmtNum(v: number): string {
|
|
79
|
+
if (!Number.isFinite(v)) return "?";
|
|
80
|
+
const a = Math.abs(v);
|
|
81
|
+
if (a >= 1e6) return `${+(v / 1e6).toFixed(1)}M`;
|
|
82
|
+
if (a >= 1e4) return `${+(v / 1e3).toFixed(1)}k`;
|
|
83
|
+
if (Number.isInteger(v)) return String(v);
|
|
84
|
+
return String(+v.toFixed(a < 1 ? 3 : a < 100 ? 2 : 1));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** monotone cubic interpolation (no overshoot) evaluated at x */
|
|
88
|
+
export function monotone(xs: number[], ys: number[]): (x: number) => number {
|
|
89
|
+
const n = xs.length;
|
|
90
|
+
if (n === 1) return () => ys[0];
|
|
91
|
+
const d: number[] = [];
|
|
92
|
+
const m: number[] = new Array(n).fill(0);
|
|
93
|
+
for (let i = 0; i < n - 1; i++) d.push((ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i] || 1));
|
|
94
|
+
m[0] = d[0];
|
|
95
|
+
m[n - 1] = d[n - 2];
|
|
96
|
+
for (let i = 1; i < n - 1; i++) m[i] = d[i - 1] * d[i] <= 0 ? 0 : (d[i - 1] + d[i]) / 2;
|
|
97
|
+
for (let i = 0; i < n - 1; i++) {
|
|
98
|
+
if (d[i] === 0) {
|
|
99
|
+
m[i] = 0;
|
|
100
|
+
m[i + 1] = 0;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const a = m[i] / d[i];
|
|
104
|
+
const b = m[i + 1] / d[i];
|
|
105
|
+
const s = a * a + b * b;
|
|
106
|
+
if (s > 9) {
|
|
107
|
+
const t = 3 / Math.sqrt(s);
|
|
108
|
+
m[i] = t * a * d[i];
|
|
109
|
+
m[i + 1] = t * b * d[i];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return (x: number) => {
|
|
113
|
+
let i = 0;
|
|
114
|
+
while (i < n - 2 && x > xs[i + 1]) i++;
|
|
115
|
+
const h = xs[i + 1] - xs[i] || 1;
|
|
116
|
+
const t = Math.min(1, Math.max(0, (x - xs[i]) / h));
|
|
117
|
+
const t2 = t * t;
|
|
118
|
+
const t3 = t2 * t;
|
|
119
|
+
return (2 * t3 - 3 * t2 + 1) * ys[i] + (t3 - 2 * t2 + t) * h * m[i] + (-2 * t3 + 3 * t2) * ys[i + 1] + (t3 - t2) * h * m[i + 1];
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The hover bubble: a one-line tooltip drawn over the cells next to the element, inside the widget's own rows so
|
|
2
|
+
// the transcript does not jump when it appears.
|
|
3
|
+
import { cell, EMPTY, textWidth, truncateText, type Cell } from "../diagram/canvas.ts";
|
|
4
|
+
import { BADGE } from "./core.ts";
|
|
5
|
+
import type { Target } from "./types.ts";
|
|
6
|
+
import { textCells } from "./util.ts";
|
|
7
|
+
|
|
8
|
+
export function drawBubble(rows: Cell[][], width: number, t: Target): void {
|
|
9
|
+
const hint = t.tip?.hint?.replace(/\s+/g, " ").trim();
|
|
10
|
+
if (!hint || !rows.length) return;
|
|
11
|
+
const text = truncateText(hint, Math.max(8, Math.min(76, width - 5)));
|
|
12
|
+
const cells: Cell[] = [cell(" ", "tip.bubble"), cell(BADGE, `tip.bubble.${t.tip?.tone ?? "info"}`), cell(" ", "tip.bubble"), ...textCells(text, "tip.bubble"), cell(" ", "tip.bubble")];
|
|
13
|
+
const w = Math.min(width, 4 + textWidth(text) + 1);
|
|
14
|
+
const r = t.rect;
|
|
15
|
+
// above the element if there is a row there, else below it, else over its own row to the right
|
|
16
|
+
let y = r.y - 1;
|
|
17
|
+
if (y < 0) y = r.y + r.h;
|
|
18
|
+
let x = Math.max(0, Math.min(r.x, width - w));
|
|
19
|
+
if (y >= rows.length) {
|
|
20
|
+
y = Math.min(r.y, rows.length - 1);
|
|
21
|
+
x = Math.max(0, Math.min(r.x + r.w + 1, width - w));
|
|
22
|
+
}
|
|
23
|
+
const row = rows[y];
|
|
24
|
+
if (!row) return;
|
|
25
|
+
// do not cut a wide character in half at either end of the bubble
|
|
26
|
+
if (row[x]?.ch === "") row[x - 1] = EMPTY;
|
|
27
|
+
const end = x + w;
|
|
28
|
+
if (row[end]?.ch === "") row[end] = EMPTY;
|
|
29
|
+
for (let i = 0; i < w && x + i < row.length; i++) row[x + i] = cells[i] ?? cell(" ", "tip.bubble");
|
|
30
|
+
}
|